﻿using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using RuoVea.ExConfig;
using System.Net.Security;
using System.Security.Claims;
using System.Text.Json;
using System.Web;

namespace RuoVea.OAuthClient;

public static class OAuthClientSetup
{
    #region AddOAuthClientSetup
    /// <summary>
    /// 
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="claims"></param>
    /// <returns></returns>
    public static AuthenticationBuilder AddOAuthClientSetup(this AuthenticationBuilder builder, string defaultScheme, Func<Dictionary<string,string>> claims = null)
    {
        builder.AddOAuthClientSetup(defaultScheme, AppSettings.GetSection("OAuthClient"), claims);
        return builder;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="claims"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static AuthenticationBuilder AddOAuthClientSetup(this AuthenticationBuilder builder, string defaultScheme, IConfiguration config, Func<Dictionary<string, string>> claims = null)
    {
        if (builder is null) throw new ArgumentNullException(nameof(builder));
        if (config == null) throw new ArgumentNullException(nameof(config));

        builder.Services.Configure<OAuthClientConfig>(AppSettings.GetSection("OAuthClient"));

        OAuthClientConfig clientOption = config.Get<OAuthClientConfig>();

        string Authority = clientOption.AuthorityUrl;

        Action<OAuthOptions> oauthOptions = (o) =>
        {
            o.SignInScheme = defaultScheme;

            o.ClientId = clientOption.ClientId;
            o.ClientSecret = clientOption.ClientSecret;

            o.AuthorizationEndpoint = $"{Authority}/oauth/authorize";
            o.TokenEndpoint = $"{Authority}/oauth/token";
            o.UserInformationEndpoint = $"{Authority}/oauth/info";
            o.CallbackPath = "/oauth-callback";

            o.UsePkce = true;
            o.ClaimActions.MapJsonKey("Name", "Name");
            o.ClaimActions.MapJsonKey(ClaimTypes.Sid, ClaimTypes.Sid);

            o.Events.OnCreatingTicket = async ctx =>
            {
                var payload = ctx.AccessToken?.Split('.')[1];
                if (!string.IsNullOrEmpty(payload))
                {
                    var json = Base64UrlTextEncoder.Decode(payload);
                    var load = JsonDocument.Parse(json);
                    ctx.RunClaimActions(load.RootElement);
                }
                await Task.CompletedTask;
            };
            o.BackchannelHttpHandler = new HttpClientHandler
            {
                // 忽略证书名称不匹配的验证（仅限测试环境）
                ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
                {
                    if (errors == SslPolicyErrors.None)
                    {
                        return true; // 无错误则验证通过
                    }
                    //// 检查证书指纹是否匹配
                    //if (cert.Thumbprint == chain.)
                    //{
                    //    // 检查证书颁发者是否匹配
                    //    if (cert.Issuer == expectedIssuer)
                    //    {
                    //        return true; // 所有条件都满足，验证通过
                    //    }
                    //}
                    return true; // 否则验证失败
                },
            };
        };

        if (claims is not null)
        {
            oauthOptions += (OAuthOptions o) =>
            {
                foreach (var item in claims())
                {
                    o.ClaimActions.MapJsonKey(item.Key, item.Value);
                }
            };
        }

        builder.AddOAuth("CustomOAuth", oauthOptions);
        return builder;
    }
    #endregion

    #region UseAuthentication_Client
    /// <summary>
    /// 添加登录已经包含 app.UseAuthentication(); 需要在 app.UseAuthorization(); 之前添加
    /// </summary>
    /// <param name="app"></param>
    /// <param name="signInUri">登录地址</param>
    /// <param name="signOutUri">登出地址</param>
    /// <returns></returns>
    public static WebApplication UseOAuthentication(this WebApplication app, string signInUri = "/account/login",string signOutUri= "/sign-out", string schemes = CookieAuthenticationDefaults.AuthenticationScheme)
    {
        app.UseAuthentication();
        app.MapGet(signInUri, (HttpContext ctx, string returnUrl, IOptions<OAuthClientConfig> clientOption) => Results.Challenge(new AuthenticationProperties() { RedirectUri = clientOption.Value.SignInUri + returnUrl }, authenticationSchemes: new List<string>() { "CustomOAuth" }));
       
        app.MapGet(signOutUri, async (HttpContext ctx, IOptions<OAuthClientConfig> clientOption) =>{
        await ctx.SignOutAsync(schemes);
        string url = $"{clientOption.Value.AuthorityUrl}/sign-out?returnUrl={HttpUtility.UrlPathEncode(clientOption.Value.SignInUri + clientOption.Value.SignOutUri)}";
        ctx.Response.Redirect(url);
        });

        return app;
    }
    #endregion

    #region 完整登出思路

    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <param name="schemes"></param>
    /// <returns></returns>
    public static async Task<RedirectResult> SignOutOAuthAsync(this HttpContext context, string schemes = CookieAuthenticationDefaults.AuthenticationScheme)
    {
        return await context.SignOutOAuthAsync(schemes);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <param name="schemes"></param>
    /// <returns></returns>
    public static async Task<RedirectResult> SignOutOAuthAsync(this HttpContext context, params string[] schemes)
    {
        IOptions<OAuthClientConfig> clientOption = context.RequestServices.GetRequiredService<IOptions<OAuthClientConfig>>();
        if (schemes.Count() > 0)
            foreach (string item in schemes)
                await context.SignOutAsync(item);

        string url = $"{clientOption.Value.AuthorityUrl}/sign-out?returnUrl={HttpUtility.UrlPathEncode(clientOption.Value.SignInUri + clientOption.Value.SignOutUri)}";
        return new RedirectResult(url);
    }
    #endregion
}
